/*
 * Please refer to the LICENSE file in the root directory of the project.
 * https://github.com/kubesphere/kubesphere/blob/master/LICENSE
 */

package params

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/emicklei/go-restful/v3"
)

const (
	PagingParam     = "paging"
	OrderByParam    = "orderBy"
	ConditionsParam = "conditions"
	ReverseParam    = "reverse"
)

const (
	DefaultLimit = 10
	DefaultPage  = 1
)

// ParsePaging parse the paging parameters from the request, then returns the limit and offset
// supported format: ?limit=1&page=1
// supported format: ?paging=limit=100,page=1
func ParsePaging(req *restful.Request) (limit, offset int) {
	paging := req.QueryParameter(PagingParam)
	limit = 10
	page := DefaultPage
	if paging != "" {
		if groups := regexp.MustCompile(`^limit=(-?\d+),page=(\d+)$`).FindStringSubmatch(paging); len(groups) == 3 {
			limit = AtoiOrDefault(groups[1], DefaultLimit)
			page = AtoiOrDefault(groups[2], DefaultPage)
		}
	} else {
		// try to parse from format ?limit=10&page=1
		limit = AtoiOrDefault(req.QueryParameter("limit"), DefaultLimit)
		page = AtoiOrDefault(req.QueryParameter("page"), DefaultPage)
	}
	offset = (page - 1) * limit

	// use the explicit offset
	if start := req.QueryParameter("start"); start != "" {
		offset = AtoiOrDefault(start, offset)
	}
	return
}

func AtoiOrDefault(str string, defVal int) int {
	if result, err := strconv.Atoi(str); err == nil {
		return result
	}
	return defVal
}

var (
	invalidKeyRegex = regexp.MustCompile(`[\s(){}\[\]]`)
)

// Ref: stdlib url.ParseQuery
func parseConditions(conditionsStr string) (*Conditions, error) {
	// string likes: key1=value1,key2~value2,key3=
	// exact query: key=value, if value is empty means label value must be ""
	// fuzzy query: key~value, if value is empty means label value is "" or label key not exist
	var conditions = &Conditions{Match: make(map[string]string, 0), Fuzzy: make(map[string]string, 0)}

	for conditionsStr != "" {
		key := conditionsStr
		if i := strings.Index(key, ","); i >= 0 {
			key, conditionsStr = key[:i], key[i+1:]
		} else {
			conditionsStr = ""
		}
		if key == "" {
			continue
		}
		value := ""
		var isFuzzy = false
		if i := strings.IndexAny(key, "~="); i >= 0 {
			if key[i] == '~' {
				isFuzzy = true
			}
			key, value = key[:i], key[i+1:]
		}
		if invalidKeyRegex.MatchString(key) {
			return nil, fmt.Errorf("invalid conditions")
		}
		if isFuzzy {
			conditions.Fuzzy[key] = value
		} else {
			conditions.Match[key] = value
		}
	}
	return conditions, nil
}

func ParseConditions(req *restful.Request) (*Conditions, error) {
	return parseConditions(req.QueryParameter(ConditionsParam))
}

type Conditions struct {
	Match map[string]string
	Fuzzy map[string]string
}

func GetBoolValueWithDefault(req *restful.Request, name string, dv bool) bool {
	reverse := req.QueryParameter(name)
	if v, err := strconv.ParseBool(reverse); err == nil {
		return v
	}
	return dv
}

func GetStringValueWithDefault(req *restful.Request, name string, dv string) string {
	v := req.QueryParameter(name)
	if v == "" {
		v = dv
	}
	return v
}
